﻿using MDK.Common;
using System;
using System.Diagnostics;
using System.Threading;

namespace MDK.Device.Components
{
    public interface IActuator : IPlcDeviceComponent
    {
        void StartForward();
        void StartRecede();
        void Stop();
        bool WaitCompleted(int timeout = int.MaxValue);
        ActuatorLocations Location { get; }
    }

    public class DelayActuator : IActuator
    {
        private DateTime _runTimestamp = DateTime.MinValue;

        public IPlcDevice Device { get; }
        public PlcAddress ForwardAddr { get; }
        public PlcAddress RecedeAddr { get; }
        public PlcAddress StopAddr { get; }
        public TimeSpan ActionDelay { get; }
        public ActuatorLocations Location { get; private set; }

        public DelayActuator(IPlcDevice device, PlcAddress forward, PlcAddress stop, PlcAddress recede, int delayMs = 0)
        {
            Device = device;
            ForwardAddr = forward;
            RecedeAddr = recede;
            StopAddr = stop;
            ActionDelay = TimeSpan.FromMilliseconds(delayMs);
        }

        public void StartForward()
        {
            if (Location == ActuatorLocations.Forwarding || Location == ActuatorLocations.Forward) return;
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(ForwardAddr);
            _runTimestamp = DateTime.Now;
            Location = ActuatorLocations.Forwarding;
            HandoverData.SandStateChangeData(EnumCmdCode.StorageMove, ((MitsubishiPlc)Device.PLC).PlcIP, this.GetType().Name, ForwardAddr.ToString(), 1);
        }

        public void StartRecede()
        {
            if (Location == ActuatorLocations.Receding || Location == ActuatorLocations.Recede) return;
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(RecedeAddr);
            _runTimestamp = DateTime.Now;
            Location = ActuatorLocations.Receding;
            HandoverData.SandStateChangeData(EnumCmdCode.StorageMove, ((MitsubishiPlc)Device.PLC).PlcIP, this.GetType().Name, ForwardAddr.ToString(), 2);
        }

        public void Stop()
        {
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            _runTimestamp = DateTime.MinValue;
            Location = ActuatorLocations.Unknown;
            HandoverData.SandStateChangeData(EnumCmdCode.StorageMove, ((MitsubishiPlc)Device.PLC).PlcIP, this.GetType().Name, ForwardAddr.ToString(), 0);
        }

        public bool WaitCompleted(int timeout = int.MaxValue)
        {
            var startTime = DateTime.Now;
            var ts = TimeSpan.FromMilliseconds(timeout);
            while ((Location == ActuatorLocations.Forwarding || Location == ActuatorLocations.Receding) 
                && DateTime.Now - _runTimestamp < ActionDelay)
            {
                Thread.Sleep(10);
                if (DateTime.Now - startTime > ts && timeout != int.MaxValue)
                {
                    return false;
                }

                while (Device.IsScrammed)
                {
                    if (FlowEventHelper.OnScrammed(Device))
                    {
                        Device.ClearError();
                        if (Location == ActuatorLocations.Forwarding)
                        {
                            Location = ActuatorLocations.Unknown;
                            StartForward();
                        }
                        else
                        {
                            Location = ActuatorLocations.Unknown;
                            StartRecede();
                        }
                        break;
                    }
                    else
                    {
                        Process.GetCurrentProcess().Kill();
                    }
                }
            }

            if (Location == ActuatorLocations.Forwarding)
            {
                Location = ActuatorLocations.Forward;
            }
            else if (Location == ActuatorLocations.Receding)
            {
                Location = ActuatorLocations.Recede;
            }

            return true;
        }
    }

    public class SensorActuator : IActuator
    {
        public IPlcDevice Device { get; }
        public PlcAddress ForwardAddr { get; }
        public PlcAddress ForwardCompletedAddr { get; }
        public PlcAddress RecedeAddr { get; }
        public PlcAddress RecedeCompletedAddr { get; }
        public PlcAddress StopAddr { get; }
        public ActuatorLocations Location { get; private set; }
        public bool ActiveValue { get; set; }
        
        public SensorActuator(IPlcDevice device, PlcAddress forward, PlcAddress forwardCompleted, PlcAddress stop, PlcAddress recede, PlcAddress recedeCompleted, bool activeVal = true)
        {
            Device = device;
            ForwardAddr = forward;
            ForwardCompletedAddr = forwardCompleted;
            RecedeAddr = recede;
            RecedeCompletedAddr = recedeCompleted;
            StopAddr = stop;
            ActiveValue = activeVal;
        }

        public void StartForward()
        {
            if (Location == ActuatorLocations.Forwarding || Location == ActuatorLocations.Forward) return;
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(ForwardAddr);
            Location = ActuatorLocations.Forwarding;
        }

        public void StartRecede()
        {
            if (Location == ActuatorLocations.Receding || Location == ActuatorLocations.Recede) return;
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(RecedeAddr);
            Location = ActuatorLocations.Receding;
        }

        public void Stop()
        {
            Device.PLC.ResetBit(ForwardAddr);
            Device.PLC.ResetBit(RecedeAddr);
            Device.PLC.ResetBit(StopAddr);
            Device.PLC.SetBit(StopAddr);
            Location = ActuatorLocations.Unknown;
        }

        public bool WaitCompleted(int timeout = int.MaxValue)
        {
            var startTime = DateTime.Now;
            var ts = TimeSpan.FromMilliseconds(timeout);
            if (Location == ActuatorLocations.Forwarding)
            {
                while (Device.PLC.BitIsSet(ForwardCompletedAddr) == ActiveValue)
                {
                    Thread.Sleep(10);
                    if (DateTime.Now - startTime > ts && timeout != int.MaxValue)
                    {
                        return false;
                    }

                    while (Device.IsScrammed)
                    {
                        if (FlowEventHelper.OnScrammed(Device))
                        {
                            Device.ClearError();
                            Location = ActuatorLocations.Unknown;
                            StartForward(); 
                            break;
                        }
                        else
                        {
                            Process.GetCurrentProcess().Kill();
                        }
                    }
                }
                Location = ActuatorLocations.Forward;
            }
            else if (Location == ActuatorLocations.Receding)
            {
                while (Device.PLC.BitIsSet(RecedeCompletedAddr) == ActiveValue)
                {
                    Thread.Sleep(10);
                    if (DateTime.Now - startTime > ts && timeout != int.MaxValue)
                    {
                        return false;
                    }

                    while (Device.IsScrammed)
                    {
                        if (FlowEventHelper.OnScrammed(Device))
                        {
                            Device.ClearError();
                            Location = ActuatorLocations.Unknown;
                            StartRecede();
                            break;
                        }
                        else
                        {
                            Process.GetCurrentProcess().Kill();
                        }
                    }
                }
                Location = ActuatorLocations.Recede;
            }

            return true;
        }
    }

    public enum ActuatorLocations
    {
        Unknown = 0,
        Forwarding = 1,
        Forward = 2,
        Receding = 3,
        Recede = 4,
    }
}